
package com.hlkj.pay.gateway.merchant.impl;
/*
 * Hlpay-Plus aggregate payment system.
 * Copyright (c) 2024-2025 Hlpay Team Copyright has the right of final interpretation.
 */

import java.io.IOException;
import java.util.Base64;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import com.hlkj.framework.common.pojo.CommonResult;
import com.hlkj.framework.extension.executor.ExtensionExecutor;
import com.hlkj.framework.extension.scenario.BizScenario;
import com.hlkj.pay.app.common.IUploadService;
import com.hlkj.pay.app.merchant.IMerchantConfigAppService;
import com.hlkj.pay.app.merchant.ext.ChannelMerchantReportExtPt;
import com.hlkj.pay.app.merchant.ext.MerchantExtConstant;
import com.hlkj.pay.app.merchant.ext.dto.payment.ChannelVirtualAccountContext;
import com.hlkj.pay.app.merchant.ext.dto.report.ChannelMerchantReportBusContext;
import com.hlkj.pay.app.merchant.ext.dto.report.ChannelMerchantReportContext;
import com.hlkj.pay.app.merchant.ext.dto.report.ChannelMerchantReportQueryContext;
import com.hlkj.pay.app.merchant.ext.dto.report.ChannelMerchantReportSignContext;
import com.hlkj.pay.common.constants.MerchantConstant;
import com.hlkj.pay.dto.merchant.VirtualBalanceDto;
import com.hlkj.pay.dto.merchant.report.MerchantReportDto;
import com.hlkj.pay.dto.merchant.report.MerchantReportResDto;
import com.hlkj.pay.dto.merchant.report.MerchantReportSmsCodeDto;
import com.hlkj.pay.dto.merchant.report.MerchantReportSmsSendDto;
import com.hlkj.pay.dto.payment.MerchantProofDto;
import com.hlkj.pay.enums.CommonEnum;
import com.hlkj.pay.enums.MerchantEnum;
import com.hlkj.pay.enums.PayEnums;
import com.hlkj.pay.gateway.merchant.IMerchantReportGateway;
import com.hlkj.pay.gateway.order.IChannelPaymentGateway;
import com.hlkj.pay.infrastructure.model.merchant.MerchantAppChannelDO;
import com.hlkj.pay.infrastructure.model.merchant.MerchantReportAmountDO;
import com.hlkj.pay.infrastructure.model.merchant.MerchantReportInfoDO;
import com.hlkj.pay.infrastructure.model.pay.PayChannelRequestDO;
import com.hlkj.pay.infrastructure.model.pay.ProviderChannelConfigDO;
import com.hlkj.pay.service.merchant.IMerchantApplicationService;
import com.hlkj.pay.service.merchant.IMerchantConfigService;
import com.hlkj.pay.service.pay.IPayChannelService;
import com.hlkj.pay.service.pay.IPayWayService;
import com.hlkj.pay.service.pay.IProviderService;
import com.hlkj.pay.util.FileUtils;

import jodd.util.StringPool;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * @author HlpayTeam
 * @date 2024/12/20 14:59
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class MerchantReportGatewayImpl implements IMerchantReportGateway {

    private final IMerchantConfigService merchantConfigService;

    private final IMerchantApplicationService merchantApplicationService;

    private final IChannelPaymentGateway channelPaymentGateway;

    private final IPayChannelService payChannelService;

    private final IPayWayService payWayService;

    private final IProviderService providerService;

    private final ExtensionExecutor extensionExecutor;

    private final IMerchantConfigAppService merchantConfigAppService;

    private final IUploadService uploadService;

    @Value("${hlkj.pay.barrier:false}")
    private boolean barrier;

    @Value("${hlkj.pay.test:false}")
    private boolean test;

    @Value("${hlkj.domain}")
    private String domain;

    @Override
    public CommonResult<MerchantReportResDto> submitChannelReport(MerchantReportDto merchantReportDto) {
        Long id = merchantReportDto.getMerchantReportInfo().getId();
        MerchantReportInfoDO merchantReportInfo = merchantReportDto.getMerchantReportInfo();
        ProviderChannelConfigDO providerChannelConfigDO = providerService.queryConfig(merchantReportInfo.getProviderCode(), merchantReportInfo.getChannelCode());
        ChannelMerchantReportContext channelMerchantReportContext = new ChannelMerchantReportContext();
        channelMerchantReportContext.setMerchantReportDto(merchantReportDto);
        channelMerchantReportContext.setProviderChannelConfig(providerChannelConfigDO);
        channelMerchantReportContext.setBeforePayChannelRequestDO(merchantReportDto.getPayChannelRequest());
        channelMerchantReportContext.setMerchantReportConfigDto(merchantConfigAppService.queryReportConfig(merchantReportInfo.getSn()));
        channelMerchantReportContext.setNotifyUrl(
                domain + MerchantExtConstant.MERCHANT_REPORT_STATUS_CALLBACK_URL + merchantReportInfo.getChannelCode() + StringPool.SLASH + merchantReportInfo.getProviderCode());
        channelMerchantReportContext.setSettleAccountNotifyUrl(domain + MerchantExtConstant.MERCHANT_REPORT_AMOUNT_STATUS_CALLBACK_URL + merchantReportInfo.getChannelCode()
                + StringPool.SLASH + merchantReportInfo.getProviderCode());
        channelMerchantReportContext.setTest(test);
        channelMerchantReportContext.setPassword(MerchantConstant.REPORT_PASSWORD);
        extensionExecutor.executeVoid(ChannelMerchantReportExtPt.class, BizScenario.valueOf(merchantReportInfo.getChannelCode()),
                ex -> ex.merchantReport(channelMerchantReportContext));
        MerchantReportResDto merchantAccessResDto = new MerchantReportResDto();
        PayChannelRequestDO payChannelRequestDO = channelMerchantReportContext.getPayChannelRequestDO();
        if (payChannelRequestDO != null) {
            payChannelRequestDO.setRelationId(merchantReportInfo.getRequestId());
            payChannelRequestDO.setSn(payChannelRequestDO.getSn());
            payChannelRequestDO.setProviderCode(merchantReportInfo.getProviderCode());
            payChannelRequestDO.setChannelCode(merchantReportInfo.getChannelCode());
            payChannelService.saveRequestLocal(payChannelRequestDO, PayEnums.CHANNEL_REQUEST_TYPE.MERCHANT_ACCESS);
            merchantAccessResDto.setApplyNo(payChannelRequestDO.getApplyNo());
        }
        if (channelMerchantReportContext.isProcessStatus()) {
            merchantAccessResDto.setReportStatus(channelMerchantReportContext.getReportStatus());
            merchantAccessResDto.setSubMchId(merchantReportInfo.getSubMchId());
            return CommonResult.success(merchantAccessResDto);
        }
        else {
            return CommonResult.error(0, channelMerchantReportContext.getRemark());
        }
    }

    @Override
    public CommonResult merchantReportSmsSend(MerchantReportInfoDO merchantReportInfoDO, MerchantReportSmsSendDto merchantReportSmsSendDto) {
        ProviderChannelConfigDO providerChannelConfigDO = providerService.queryConfig(merchantReportInfoDO.getProviderCode(), merchantReportInfoDO.getChannelCode());
        ChannelMerchantReportSignContext channelMerchantReportSignContext = new ChannelMerchantReportSignContext();
        channelMerchantReportSignContext.setMerchantReportInfo(merchantReportInfoDO);
        channelMerchantReportSignContext.setProviderChannelConfig(providerChannelConfigDO);
        channelMerchantReportSignContext.setMerchantReportSmsSendDto(merchantReportSmsSendDto);
        channelMerchantReportSignContext.setTest(test);
        extensionExecutor.executeVoid(ChannelMerchantReportExtPt.class, BizScenario.valueOf(merchantReportInfoDO.getChannelCode()),
                ex -> ex.merchantReportSign(channelMerchantReportSignContext));
        PayChannelRequestDO payChannelRequestDO = channelMerchantReportSignContext.getPayChannelRequestDO();
        if (payChannelRequestDO != null) {
            payChannelRequestDO.setRelationId(merchantReportInfoDO.getRequestId());
            payChannelRequestDO.setSn(payChannelRequestDO.getSn());
            payChannelRequestDO.setProviderCode(providerChannelConfigDO.getProviderCode());
            payChannelRequestDO.setChannelCode(providerChannelConfigDO.getChannelCode());
            payChannelService.saveRequest(payChannelRequestDO, PayEnums.CHANNEL_REQUEST_TYPE.MERCHANT_ACCESS);
        }
        if (channelMerchantReportSignContext.isProcessStatus()) {
            return CommonResult.success();
        }
        else {
            return CommonResult.error(0, channelMerchantReportSignContext.getRemark());
        }
    }

    @Override
    public CommonResult merchantReportSmsCode(MerchantReportInfoDO merchantReportInfoDO, MerchantReportSmsCodeDto merchantReportSmsCodeDto) {
        ProviderChannelConfigDO providerChannelConfigDO = providerService.queryConfig(merchantReportInfoDO.getProviderCode(), merchantReportInfoDO.getChannelCode());
        ChannelMerchantReportSignContext channelMerchantReportSignContext = new ChannelMerchantReportSignContext();
        channelMerchantReportSignContext.setMerchantReportInfo(merchantReportInfoDO);
        channelMerchantReportSignContext.setProviderChannelConfig(providerChannelConfigDO);
        channelMerchantReportSignContext.setMerchantReportSmsCodeDto(merchantReportSmsCodeDto);
        channelMerchantReportSignContext.setTest(test);
        extensionExecutor.executeVoid(ChannelMerchantReportExtPt.class, BizScenario.valueOf(merchantReportInfoDO.getChannelCode()),
                ex -> ex.merchantReportSign(channelMerchantReportSignContext));
        PayChannelRequestDO payChannelRequestDO = channelMerchantReportSignContext.getPayChannelRequestDO();
        if (payChannelRequestDO != null) {
            payChannelRequestDO.setRelationId(merchantReportInfoDO.getRequestId());
            payChannelRequestDO.setSn(payChannelRequestDO.getSn());
            payChannelRequestDO.setProviderCode(providerChannelConfigDO.getProviderCode());
            payChannelRequestDO.setChannelCode(providerChannelConfigDO.getChannelCode());
            payChannelService.saveRequest(payChannelRequestDO, PayEnums.CHANNEL_REQUEST_TYPE.MERCHANT_ACCESS);
        }
        if (channelMerchantReportSignContext.isProcessStatus()) {
            return CommonResult.success();
        }
        else {
            return CommonResult.error(0, channelMerchantReportSignContext.getRemark());
        }
    }

    @Override
    public CommonResult merchantReportQuery(MerchantReportInfoDO merchantReportInfoDO) {
        ProviderChannelConfigDO providerChannelConfigDO = providerService.queryConfig(merchantReportInfoDO.getProviderCode(), merchantReportInfoDO.getChannelCode());
        ChannelMerchantReportQueryContext channelMerchantReportQueryContext = new ChannelMerchantReportQueryContext();
        channelMerchantReportQueryContext.setMerchantReportInfo(merchantReportInfoDO);
        channelMerchantReportQueryContext.setProviderChannelConfig(providerChannelConfigDO);
        channelMerchantReportQueryContext.setTest(test);
        extensionExecutor.executeVoid(ChannelMerchantReportExtPt.class, BizScenario.valueOf(merchantReportInfoDO.getChannelCode()),
                ex -> ex.merchantReportQuery(channelMerchantReportQueryContext));
        if (channelMerchantReportQueryContext.isProcessStatus()) {
            MerchantReportResDto merchantReportResDto = new MerchantReportResDto();
            merchantReportResDto.setReportStatus(channelMerchantReportQueryContext.getReportStatus());
            merchantReportResDto.setReportBankStatus(channelMerchantReportQueryContext.getReportBankStatus());
            merchantReportResDto.setExtendA(merchantReportInfoDO.getExtendA());
            merchantReportResDto.setVirtualAccountNo(merchantReportInfoDO.getVirtualAccountNo());
            merchantReportResDto.setBankCardId(merchantReportInfoDO.getBankCardId());
            merchantReportResDto.setSignLink(merchantReportInfoDO.getSignLink());
            return CommonResult.success(merchantReportResDto);
        }
        else {
            return CommonResult.error(0, channelMerchantReportQueryContext.getRemark());
        }
    }

    @Override
    public CommonResult<MerchantReportResDto> merchantReportSign(MerchantReportInfoDO merchantReportInfoDO) {
        ProviderChannelConfigDO providerChannelConfigDO = providerService.queryConfig(merchantReportInfoDO.getProviderCode(), merchantReportInfoDO.getChannelCode());
        ChannelMerchantReportSignContext channelMerchantReportQueryContext = new ChannelMerchantReportSignContext();
        channelMerchantReportQueryContext.setMerchantReportInfo(merchantReportInfoDO);
        channelMerchantReportQueryContext.setProviderChannelConfig(providerChannelConfigDO);
        channelMerchantReportQueryContext.setTest(test);
        extensionExecutor.executeVoid(ChannelMerchantReportExtPt.class, BizScenario.valueOf(merchantReportInfoDO.getChannelCode()),
                ex -> ex.merchantReportSign(channelMerchantReportQueryContext));
        if (channelMerchantReportQueryContext.isProcessStatus()) {
            MerchantReportResDto merchantReportResDto = new MerchantReportResDto();
            merchantReportResDto.setReportStatus(channelMerchantReportQueryContext.getReportStatus());
            merchantReportResDto.setReportBankStatus(channelMerchantReportQueryContext.getReportBankStatus());
            merchantReportResDto.setExtendA(merchantReportInfoDO.getExtendA());
            merchantReportResDto.setVirtualAccountNo(merchantReportInfoDO.getVirtualAccountNo());
            merchantReportResDto.setBankCardId(merchantReportInfoDO.getBankCardId());
            merchantReportResDto.setSignLink(merchantReportInfoDO.getSignLink());
            return CommonResult.success(merchantReportResDto);
        }
        else {
            return CommonResult.error(0, channelMerchantReportQueryContext.getRemark());
        }
    }

    @Override
    public CommonResult merchantReportSignQuery(MerchantReportInfoDO merchantReportInfoDO) {
        ProviderChannelConfigDO providerChannelConfigDO = providerService.queryConfig(merchantReportInfoDO.getProviderCode(), merchantReportInfoDO.getChannelCode());
        ChannelMerchantReportQueryContext channelMerchantReportQueryContext = new ChannelMerchantReportQueryContext();
        channelMerchantReportQueryContext.setMerchantReportInfo(merchantReportInfoDO);
        channelMerchantReportQueryContext.setProviderChannelConfig(providerChannelConfigDO);
        channelMerchantReportQueryContext.setTest(test);
        extensionExecutor.executeVoid(ChannelMerchantReportExtPt.class, BizScenario.valueOf(merchantReportInfoDO.getChannelCode()),
                ex -> ex.merchantReportSignQuery(channelMerchantReportQueryContext));
        if (channelMerchantReportQueryContext.isProcessStatus()) {
            MerchantReportResDto merchantReportResDto = new MerchantReportResDto();
            merchantReportResDto.setReportStatus(channelMerchantReportQueryContext.getReportStatus());
            merchantReportResDto.setReportBankStatus(channelMerchantReportQueryContext.getReportBankStatus());
            merchantReportResDto.setExtendA(merchantReportInfoDO.getExtendA());
            merchantReportResDto.setVirtualAccountNo(merchantReportInfoDO.getVirtualAccountNo());
            merchantReportResDto.setBankCardId(merchantReportInfoDO.getBankCardId());
            // merchantReportResDto.setSignLink(merchantReportInfoDO.getSignLink());
            return CommonResult.success(merchantReportResDto);
        }
        else {
            return CommonResult.error(0, channelMerchantReportQueryContext.getRemark());
        }
    }

    @Override
    public boolean extensionExist(String channelCode) {
        return extensionExecutor.executeExist(ChannelMerchantReportExtPt.class, BizScenario.valueOf(channelCode));
    }

    @Override
    public CommonResult<MerchantReportResDto> merchantReportAllot(MerchantReportInfoDO merchantReportInfoDO, MerchantReportAmountDO merchantReportAmountDO) {
        MerchantAppChannelDO merchantAppChannelDO = merchantApplicationService.queryDetailByAccessId(merchantReportInfoDO.getId(), MerchantEnum.DATA_RELATION_TYPE.REPORT);

        ChannelMerchantReportBusContext channelMerchantReportBusContext = new ChannelMerchantReportBusContext();
        channelMerchantReportBusContext.setProviderChannelConfig(providerService.queryConfig(merchantAppChannelDO.getProviderCode(), merchantAppChannelDO.getChannelCode()));
        channelMerchantReportBusContext.setMerchantAppChannelDO(merchantAppChannelDO);
        channelMerchantReportBusContext.setMerchantReportAmountDO(merchantReportAmountDO);
        extensionExecutor.executeVoid(ChannelMerchantReportExtPt.class, BizScenario.valueOf(merchantReportInfoDO.getChannelCode()),
                ex -> ex.merchantReportAllot(channelMerchantReportBusContext));
        PayChannelRequestDO payChannelRequestDO = channelMerchantReportBusContext.getPayChannelRequestDO();
        if (payChannelRequestDO != null) {
            payChannelRequestDO.setRelationId(merchantReportInfoDO.getRequestId());
            payChannelRequestDO.setSn(payChannelRequestDO.getSn());
            payChannelRequestDO.setProviderCode(merchantAppChannelDO.getProviderCode());
            payChannelRequestDO.setChannelCode(merchantAppChannelDO.getChannelCode());
            payChannelService.saveRequest(payChannelRequestDO, PayEnums.CHANNEL_REQUEST_TYPE.MERCHANT_ACCESS);
        }
        if (channelMerchantReportBusContext.isProcessStatus()) {
            return CommonResult.success();
        }
        else {
            return CommonResult.error(0, channelMerchantReportBusContext.getRemark());
        }
    }

    @Override
    public MerchantReportAmountDO merchantReportAllotQuery(MerchantReportInfoDO merchantReportInfoDO, MerchantReportAmountDO merchantReportAmountDO) {
        ProviderChannelConfigDO providerChannelConfigDO = providerService.queryConfig(merchantReportInfoDO.getProviderCode(), merchantReportInfoDO.getChannelCode());
        ChannelMerchantReportBusContext channelMerchantReportQueryContext = new ChannelMerchantReportBusContext();
        channelMerchantReportQueryContext.setMerchantReportAmountDO(merchantReportAmountDO);
        channelMerchantReportQueryContext.setProviderChannelConfig(providerChannelConfigDO);
        channelMerchantReportQueryContext.setTest(test);
        extensionExecutor.executeVoid(ChannelMerchantReportExtPt.class, BizScenario.valueOf(merchantReportInfoDO.getChannelCode()),
                ex -> ex.merchantReportAllotQuery(channelMerchantReportQueryContext));
        return channelMerchantReportQueryContext.getUdpateMerchantReportAmountDO();
    }

    @Override
    public MerchantProofDto queryAllotProof(MerchantReportInfoDO merchantReportInfoDO, MerchantReportAmountDO merchantReportAmountDO) {
        MerchantProofDto merchantProofDto = new MerchantProofDto();
        if (barrier) {
            log.info("测试环境查询调拨凭证状态 detailPaymentId:{}", merchantReportAmountDO.getCode());
            return merchantProofDto;
        }
        ProviderChannelConfigDO providerChannelConfigDO = providerService.queryConfig(merchantReportInfoDO.getProviderCode(), merchantReportInfoDO.getChannelCode());
        ChannelMerchantReportBusContext channelMerchantReportQueryContext = new ChannelMerchantReportBusContext();
        channelMerchantReportQueryContext.setMerchantReportAmountDO(merchantReportAmountDO);
        channelMerchantReportQueryContext.setProviderChannelConfig(providerChannelConfigDO);
        channelMerchantReportQueryContext.setTest(test);
        extensionExecutor.executeVoid(ChannelMerchantReportExtPt.class, BizScenario.valueOf(merchantReportInfoDO.getChannelCode()),
                ex -> ex.merchantReportAllotProof(channelMerchantReportQueryContext));
        if (channelMerchantReportQueryContext.isProcessStatus()) {
            StringUtils.hasText(channelMerchantReportQueryContext.getVoucherFileStream());
            StringUtils.hasText(channelMerchantReportQueryContext.getVoucherFileUrl());
            // 解码 Base64 编码的 PDF 数据
            byte[] proof = null;
            if (StringUtils.hasText(channelMerchantReportQueryContext.getVoucherFileStream())) {
                // 解码 Base64 编码的 PDF 数据
                proof = Base64.getDecoder().decode(channelMerchantReportQueryContext.getVoucherFileStream());
            }
            else if (StringUtils.hasText(channelMerchantReportQueryContext.getVoucherFileUrl())) {
                try {
                    proof = FileUtils.fileBytes(channelMerchantReportQueryContext.getVoucherFileUrl());
                }
                catch (IOException e) {
                    log.error("调拨凭证获取HTTP 文件失败:{}", e.getMessage());
                    return merchantProofDto;
                }
            }
            else if (channelMerchantReportQueryContext.getVoucherFileByte() != null && channelMerchantReportQueryContext.getVoucherFileByte().length > 0) {
                proof = channelMerchantReportQueryContext.getVoucherFileByte();
            }
            if (proof != null && proof.length > 0) {
                CommonResult<String> stringCommonResult = uploadService.uploadLocalFile(proof, merchantReportAmountDO.getCode(), CommonEnum.OssEnum.PDF);
                if (stringCommonResult.isSuccess()) {
                    merchantProofDto.setProofPath(stringCommonResult.getData());
                    return merchantProofDto;
                }
            }
            else {
                merchantProofDto.setMsg(channelMerchantReportQueryContext.getRemark());
                return merchantProofDto;
            }
        }
        else {
            if (StringUtils.hasText(channelMerchantReportQueryContext.getFileId())) {
                merchantProofDto.setProofId(channelMerchantReportQueryContext.getFileId());
            }
            merchantProofDto.setMsg(channelMerchantReportQueryContext.getRemark());
        }
        return merchantProofDto;
    }

    @Override
    public CommonResult<VirtualBalanceDto> balanceQuery(String mchChannelCode) {
        if (barrier) {
            VirtualBalanceDto orderTransfer = new VirtualBalanceDto();
            log.info("测试环境转账余额查询 返回成功 payOrderNo:{}", mchChannelCode);
            return CommonResult.success(orderTransfer);
        }
        MerchantAppChannelDO merchantAppChannelDO = merchantApplicationService.queryAppChannelDetail(mchChannelCode);
        ChannelVirtualAccountContext channelTransferContext = new ChannelVirtualAccountContext();
        channelTransferContext.setMerchantAppChannelDO(merchantAppChannelDO);
        channelTransferContext.setProviderChannelConfig(providerService.queryConfig(merchantAppChannelDO.getProviderCode(), merchantAppChannelDO.getChannelCode()));
        channelTransferContext.setTest(test);
        extensionExecutor.executeVoid(ChannelMerchantReportExtPt.class, BizScenario.valueOf(merchantAppChannelDO.getChannelCode()),
                ex -> ex.queryChannelAccount(channelTransferContext));
        if (channelTransferContext.isProcessStatus()) {
            return CommonResult.success(channelTransferContext.getVirtualBalanceDto());
        }
        return CommonResult.error(1, channelTransferContext.getRemark());
    }

    @Override
    public CommonResult<String> getSignUrl(MerchantReportInfoDO merchantReportInfoDO) {
        ProviderChannelConfigDO providerChannelConfigDO = providerService.queryConfig(merchantReportInfoDO.getProviderCode(), merchantReportInfoDO.getChannelCode());
        ChannelMerchantReportSignContext channelMerchantReportSignContext = new ChannelMerchantReportSignContext();
        channelMerchantReportSignContext.setMerchantReportInfo(merchantReportInfoDO);
        channelMerchantReportSignContext.setProviderChannelConfig(providerChannelConfigDO);
        channelMerchantReportSignContext.setTest(test);
        extensionExecutor.executeVoid(ChannelMerchantReportExtPt.class, BizScenario.valueOf(merchantReportInfoDO.getChannelCode()),
                ex -> ex.getMerchantReportSign(channelMerchantReportSignContext));
        if (channelMerchantReportSignContext.isProcessStatus()) {
            return CommonResult.success(merchantReportInfoDO.getSignLink());
        }
        else {
            return CommonResult.error(0, channelMerchantReportSignContext.getRemark());
        }
    }
}
